/*
* Copyright © 2014-2015 Cask Data, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package co.cask.cdap.gateway.router;
import co.cask.cdap.common.conf.Constants;
import co.cask.cdap.common.discovery.ResolvingDiscoverable;
import co.cask.cdap.common.utils.Networks;
import co.cask.http.AbstractHttpHandler;
import co.cask.http.ChunkResponder;
import co.cask.http.HttpResponder;
import co.cask.http.NettyHttpService;
import com.google.common.base.Charsets;
import com.google.common.base.Function;
import com.google.common.base.Splitter;
import com.google.common.base.Supplier;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.AbstractIdleService;
import com.ning.http.client.AsyncCompletionHandler;
import com.ning.http.client.AsyncHttpClient;
import com.ning.http.client.AsyncHttpClientConfig;
import com.ning.http.client.HttpResponseBodyPart;
import com.ning.http.client.Request;
import com.ning.http.client.RequestBuilder;
import com.ning.http.client.Response;
import com.ning.http.client.providers.netty.NettyAsyncHttpProvider;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicHeader;
import org.apache.http.util.EntityUtils;
import org.apache.twill.common.Cancellable;
import org.apache.twill.discovery.Discoverable;
import org.apache.twill.discovery.DiscoveryService;
import org.apache.twill.discovery.DiscoveryServiceClient;
import org.apache.twill.discovery.InMemoryDiscoveryService;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nullable;
import javax.net.SocketFactory;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
/**
* Tests Netty Router.
*/
public abstract class NettyRouterTestBase {
protected static final String HOSTNAME = "127.0.0.1";
protected static final DiscoveryService DISCOVERY_SERVICE = new InMemoryDiscoveryService();
protected static final String DEFAULT_SERVICE = Constants.Router.GATEWAY_DISCOVERY_NAME;
protected static final String WEBAPP_SERVICE = Constants.Router.WEBAPP_DISCOVERY_NAME;
protected static final String APP_FABRIC_SERVICE = Constants.Service.APP_FABRIC_HTTP;
protected static final String WEB_APP_SERVICE_PREFIX = "webapp/";
protected static final int CONNECTION_IDLE_TIMEOUT_SECS = 2;
private static final Logger LOG = LoggerFactory.getLogger(NettyRouterTestBase.class);
private static final int MAX_UPLOAD_BYTES = 10 * 1024 * 1024;
private static final int CHUNK_SIZE = 1024 * 1024; // NOTE: MAX_UPLOAD_BYTES % CHUNK_SIZE == 0
private final Supplier<String> defaultServiceSupplier = new Supplier<String>() {
@Override
public String get() {
return APP_FABRIC_SERVICE;
}
};
private final Supplier<String> webappServiceSupplier = new Supplier<String>() {
@Override
public String get() {
try {
return WEB_APP_SERVICE_PREFIX + Networks.normalizeWebappDiscoveryName(HOSTNAME + ":" +
lookupService(WEBAPP_SERVICE));
} catch (UnsupportedEncodingException e) {
LOG.error("Got exception: ", e);
throw Throwables.propagate(e);
}
}
};
private final Supplier<String> defaultWebappServiceSupplier1 = new Supplier<String>() {
@Override
public String get() {
try {
return WEB_APP_SERVICE_PREFIX + Networks.normalizeWebappDiscoveryName("default/abc");
} catch (UnsupportedEncodingException e) {
LOG.error("Got exception: ", e);
throw Throwables.propagate(e);
}
}
};
private final Supplier<String> defaultWebappServiceSupplier2 = new Supplier<String>() {
@Override
public String get() {
try {
return WEB_APP_SERVICE_PREFIX + Networks.normalizeWebappDiscoveryName("default/def");
} catch (UnsupportedEncodingException e) {
LOG.error("Got exception: ", e);
throw Throwables.propagate(e);
}
}
};
public final RouterService routerService = createRouterService();
public final ServerService defaultServer1 = new ServerService(HOSTNAME, DISCOVERY_SERVICE, defaultServiceSupplier);
public final ServerService defaultServer2 = new ServerService(HOSTNAME, DISCOVERY_SERVICE, defaultServiceSupplier);
public final ServerService webappServer = new ServerService(HOSTNAME, DISCOVERY_SERVICE, webappServiceSupplier);
public final ServerService defaultWebappServer1 = new ServerService(HOSTNAME, DISCOVERY_SERVICE,
defaultWebappServiceSupplier1);
public final ServerService defaultWebappServer2 = new ServerService(HOSTNAME, DISCOVERY_SERVICE,
defaultWebappServiceSupplier2);
public final List<ServerService> allServers = Lists.newArrayList(defaultServer1, defaultServer2, webappServer,
defaultWebappServer1, defaultWebappServer2);
protected abstract RouterService createRouterService();
protected abstract String getProtocol();
protected abstract DefaultHttpClient getHTTPClient() throws Exception;
protected abstract SocketFactory getSocketFactory() throws Exception;
protected int lookupService(String serviceName) {
return routerService.lookupService(serviceName);
}
private String resolveURI(String serviceName, String path) throws URISyntaxException {
return getBaseURI(serviceName).resolve(path).toASCIIString();
}
private URI getBaseURI(String serviceName) throws URISyntaxException {
int servicePort = lookupService(serviceName);
return new URI(String.format("%s://%s:%d", getProtocol(), HOSTNAME, servicePort));
}
@Before
public void startUp() throws Exception {
routerService.startAndWait();
for (ServerService server : allServers) {
server.clearState();
server.startAndWait();
}
// Wait for both servers of defaultService to be registered
Iterable<Discoverable> discoverables = ((DiscoveryServiceClient) DISCOVERY_SERVICE).discover(
defaultServiceSupplier.get());
for (int i = 0; i < 50 && Iterables.size(discoverables) != 2; ++i) {
TimeUnit.MILLISECONDS.sleep(50);
}
// Wait for server of webappService to be registered
discoverables = ((DiscoveryServiceClient) DISCOVERY_SERVICE).discover(webappServiceSupplier.get());
for (int i = 0; i < 50 && Iterables.size(discoverables) != 1; ++i) {
TimeUnit.MILLISECONDS.sleep(50);
}
// Wait for server of defaultWebappServiceSupplier1 to be registered
discoverables = ((DiscoveryServiceClient) DISCOVERY_SERVICE).discover(defaultWebappServiceSupplier1.get());
for (int i = 0; i < 50 && Iterables.size(discoverables) != 1; ++i) {
TimeUnit.MILLISECONDS.sleep(50);
}
// Wait for server of defaultWebappServiceSupplier2 to be registered
discoverables = ((DiscoveryServiceClient) DISCOVERY_SERVICE).discover(defaultWebappServiceSupplier2.get());
for (int i = 0; i < 50 && Iterables.size(discoverables) != 1; ++i) {
TimeUnit.MILLISECONDS.sleep(50);
}
}
@After
public void tearDown() {
for (ServerService server : allServers) {
server.stopAndWait();
}
routerService.stopAndWait();
}
@Test
public void testRouterSync() throws Exception {
testSync(25);
// sticky endpoint strategy used so the sum should be 25
Assert.assertEquals(25, defaultServer1.getNumRequests() + defaultServer2.getNumRequests());
}
@Test
public void testRouterAsync() throws Exception {
int numElements = 123;
AsyncHttpClientConfig.Builder configBuilder = new AsyncHttpClientConfig.Builder();
final AsyncHttpClient asyncHttpClient = new AsyncHttpClient(
new NettyAsyncHttpProvider(configBuilder.build()),
configBuilder.build());
final CountDownLatch latch = new CountDownLatch(numElements);
final AtomicInteger numSuccessfulRequests = new AtomicInteger(0);
for (int i = 0; i < numElements; ++i) {
final int elem = i;
final Request request = new RequestBuilder("GET")
.setUrl(resolveURI(DEFAULT_SERVICE, String.format("%s/%s-%d", "/v1/ping", "async", i)))
.build();
asyncHttpClient.executeRequest(request,
new AsyncCompletionHandler<Void>() {
@Override
public Void onCompleted(Response response) throws Exception {
latch.countDown();
Assert.assertEquals(HttpResponseStatus.OK.getCode(),
response.getStatusCode());
numSuccessfulRequests.incrementAndGet();
return null;
}
@Override
public void onThrowable(Throwable t) {
LOG.error("Got exception while posting {}", elem, t);
latch.countDown();
}
});
// Sleep so as not to overrun the server.
TimeUnit.MILLISECONDS.sleep(1);
}
latch.await();
asyncHttpClient.close();
Assert.assertEquals(numElements, numSuccessfulRequests.get());
// we use sticky endpoint strategy so the sum of requests from the two gateways should be NUM_ELEMENTS
Assert.assertTrue(numElements == (defaultServer1.getNumRequests() + defaultServer2.getNumRequests()));
}
@Test
public void testRouterOneServerDown() throws Exception {
try {
// Bring down defaultServer1
defaultServer1.cancelRegistration();
testSync(25);
} finally {
Assert.assertEquals(0, defaultServer1.getNumRequests());
Assert.assertTrue(defaultServer2.getNumRequests() > 0);
defaultServer1.registerServer();
}
}
@Test
public void testRouterAllServersDown() throws Exception {
try {
// Bring down all servers
defaultServer1.cancelRegistration();
defaultServer2.cancelRegistration();
testSyncServiceUnavailable();
} finally {
Assert.assertEquals(0, defaultServer1.getNumRequests());
Assert.assertEquals(0, defaultServer2.getNumRequests());
defaultServer1.registerServer();
defaultServer2.registerServer();
}
}
@Test
public void testHostForward() throws Exception {
// Test defaultService
HttpResponse response = get(resolveURI(DEFAULT_SERVICE, String.format("%s/%s", "/v1/ping", "sync")));
Assert.assertEquals(HttpResponseStatus.OK.getCode(), response.getStatusLine().getStatusCode());
Assert.assertEquals(defaultServiceSupplier.get(), EntityUtils.toString(response.getEntity()));
// Test webappService
response = get(resolveURI(WEBAPP_SERVICE, String.format("%s/%s", "/v1/ping", "sync")));
Assert.assertEquals(HttpResponseStatus.OK.getCode(), response.getStatusLine().getStatusCode());
Assert.assertEquals(webappServiceSupplier.get(), EntityUtils.toString(response.getEntity()));
// Test default
response = get(resolveURI(WEBAPP_SERVICE, String.format("%s/%s", "/abc/v1/ping", "sync")),
new Header[]{new BasicHeader(HttpHeaders.Names.HOST, "www.abc.com")});
Assert.assertEquals(HttpResponseStatus.OK.getCode(), response.getStatusLine().getStatusCode());
Assert.assertEquals(defaultWebappServiceSupplier1.get(), EntityUtils.toString(response.getEntity()));
// Test default, port 80
response = get(resolveURI(WEBAPP_SERVICE, String.format("%s/%s", "/abc/v1/ping", "sync")),
new Header[]{new BasicHeader(HttpHeaders.Names.HOST, "www.def.com" + ":80")});
Assert.assertEquals(HttpResponseStatus.OK.getCode(), response.getStatusLine().getStatusCode());
Assert.assertEquals(defaultWebappServiceSupplier1.get(), EntityUtils.toString(response.getEntity()));
// Test default, port random port
response = get(resolveURI(WEBAPP_SERVICE, String.format("%s/%s", "/def/v1/ping", "sync")),
new Header[]{new BasicHeader(HttpHeaders.Names.HOST, "www.ghi.net" + ":" + "5678")});
Assert.assertEquals(HttpResponseStatus.OK.getCode(), response.getStatusLine().getStatusCode());
Assert.assertEquals(defaultWebappServiceSupplier2.get(), EntityUtils.toString(response.getEntity()));
}
@Test
public void testUpload() throws Exception {
AsyncHttpClientConfig.Builder configBuilder = new AsyncHttpClientConfig.Builder();
final AsyncHttpClient asyncHttpClient = new AsyncHttpClient(
new NettyAsyncHttpProvider(configBuilder.build()),
configBuilder.build());
byte [] requestBody = generatePostData();
final Request request = new RequestBuilder("POST")
.setUrl(resolveURI(DEFAULT_SERVICE, "/v1/upload"))
.setContentLength(requestBody.length)
.setBody(new ByteEntityWriter(requestBody))
.build();
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Future<Void> future = asyncHttpClient.executeRequest(request, new AsyncCompletionHandler<Void>() {
@Override
public Void onCompleted(Response response) throws Exception {
return null;
}
@Override
public STATE onBodyPartReceived(HttpResponseBodyPart content) throws Exception {
//TimeUnit.MILLISECONDS.sleep(RANDOM.nextInt(10));
content.writeTo(byteArrayOutputStream);
return super.onBodyPartReceived(content);
}
});
future.get();
Assert.assertArrayEquals(requestBody, byteArrayOutputStream.toByteArray());
}
@Test
public void testConnectionClose() throws Exception {
URL[] urls = new URL[] {
new URL(resolveURI(Constants.Router.GATEWAY_DISCOVERY_NAME, "/abc/v1/status")),
new URL(resolveURI(Constants.Router.GATEWAY_DISCOVERY_NAME, "/def/v1/status"))
};
// Make bunch of requests to one service to 2 difference urls, with the first one keep-alive, second one not.
// This make router creates two backend service connections on the same inbound connection
// This is to verify on the close of the second one, it won't close the the inbound if there is an
// in-flight request happening already (if reached another round of the following for-loop).
int times = 1000;
boolean keepAlive = true;
for (int i = 0; i < times; i++) {
HttpURLConnection urlConn = openURL(urls[i % urls.length]);
try {
urlConn.setRequestProperty(HttpHeaders.Names.CONNECTION,
keepAlive ? HttpHeaders.Values.KEEP_ALIVE : HttpHeaders.Values.CLOSE);
Assert.assertEquals(HttpURLConnection.HTTP_OK, urlConn.getResponseCode());
} finally {
keepAlive = !keepAlive;
urlConn.disconnect();
}
}
Assert.assertEquals(times, defaultServer1.getNumRequests() + defaultServer2.getNumRequests());
}
// have a timeout of 10 seconds, in case the final call to reader.read hangs (in the case that connection isn't
// disconnected)
@Test(timeout = 10000)
public void testConnectionIdleTimeout() throws Exception {
defaultServer2.cancelRegistration();
String path = "/v2/ping";
URI uri = new URI(resolveURI(Constants.Router.GATEWAY_DISCOVERY_NAME, path));
Socket socket = getSocketFactory().createSocket(uri.getHost(), uri.getPort());
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
InputStream inputStream = socket.getInputStream();
// make a request
String firstLine = makeRequest(uri, out, inputStream);
Assert.assertEquals("HTTP/1.1 200 OK\r", firstLine);
// sleep for 500 ms below the configured idle timeout; the connection on server side should not get closed by then
TimeUnit.MILLISECONDS.sleep(TimeUnit.SECONDS.toMillis(CONNECTION_IDLE_TIMEOUT_SECS) - 500);
firstLine = makeRequest(uri, out, inputStream);
Assert.assertEquals("HTTP/1.1 200 OK\r", firstLine);
// sleep for 500 ms over the configured idle timeout; the connection on server side should get closed by then
TimeUnit.MILLISECONDS.sleep(TimeUnit.SECONDS.toMillis(CONNECTION_IDLE_TIMEOUT_SECS) + 500);
// Due to timeout the client connection will be closed, and hence this request should not go to the server
makeRequest(uri, out, inputStream);
// assert that the connection is closed on the server side
Assert.assertEquals(2, defaultServer1.getNumRequests() + defaultServer2.getNumRequests());
Assert.assertEquals(1, defaultServer1.getNumConnectionsOpened() + defaultServer2.getNumConnectionsOpened());
Assert.assertEquals(1, defaultServer1.getNumConnectionsClosed() + defaultServer2.getNumConnectionsClosed());
}
private String makeRequest(URI uri, PrintWriter out, InputStream inputStream) throws IOException {
//Send request
out.print("GET " + uri.getPath() + " HTTP/1.1\r\n" +
"Host: " + uri.getHost() + "\r\n" +
"Connection: keep-alive\r\n\r\n");
out.flush();
byte[] buffer = new byte[1024];
int length = 0;
for (int i = 0; i < 20; ++i) {
int read = inputStream.read(buffer, length, 1024 - length);
length += read < 0 ? 0 : read;
// Output returned is 108 bytes in length for /v2/ping
if (length >= 108) {
break;
}
}
// Return only first line of the response
return Iterables.getFirst(Splitter.on("\n").split(new String(buffer, Charsets.UTF_8.name())), "");
}
@Test
public void testConnectionIdleTimeoutWithMultipleServers() throws Exception {
defaultServer2.cancelRegistration();
URL url = new URL(resolveURI(Constants.Router.GATEWAY_DISCOVERY_NAME, "/v2/ping"));
HttpURLConnection urlConnection = openURL(url);
Assert.assertEquals(200, urlConnection.getResponseCode());
urlConnection.getInputStream().close();
urlConnection.disconnect();
// requests past this point will go to defaultServer2
defaultServer1.cancelRegistration();
defaultServer2.registerServer();
for (int i = 0; i < 4; i++) {
// this is an assumption that CONNECTION_IDLE_TIMEOUT_SECS is more than 1 second
TimeUnit.SECONDS.sleep(1);
url = new URL(resolveURI(Constants.Router.GATEWAY_DISCOVERY_NAME, "/v1/ping/" + i));
urlConnection = openURL(url);
Assert.assertEquals(200, urlConnection.getResponseCode());
urlConnection.getInputStream().close();
urlConnection.disconnect();
}
// for the past 4 seconds, we've been making requests to defaultServer2; therefore, defaultServer1 will have closed
// its single connection
Assert.assertEquals(1, defaultServer1.getNumConnectionsOpened());
Assert.assertEquals(1, defaultServer1.getNumConnectionsClosed());
// however, the connection to defaultServer2 is not timed out, because we've been making requests to it
Assert.assertEquals(1, defaultServer2.getNumConnectionsOpened());
Assert.assertEquals(0, defaultServer2.getNumConnectionsClosed());
defaultServer2.registerServer();
defaultServer1.cancelRegistration();
url = new URL(resolveURI(Constants.Router.GATEWAY_DISCOVERY_NAME, "/v2/ping"));
urlConnection = openURL(url);
Assert.assertEquals(200, urlConnection.getResponseCode());
urlConnection.getInputStream().close();
urlConnection.disconnect();
}
@Test
public void testConnectionNoIdleTimeout() throws Exception {
// even though the handler will sleep for 500ms over the configured idle timeout before responding, the connection
// is not closed because the http request is in progress
long timeoutMillis = TimeUnit.SECONDS.toMillis(CONNECTION_IDLE_TIMEOUT_SECS) + 500;
URL url = new URL(resolveURI(Constants.Router.GATEWAY_DISCOVERY_NAME, "/v1/timeout/" + timeoutMillis));
HttpURLConnection urlConnection = openURL(url);
Assert.assertEquals(200, urlConnection.getResponseCode());
urlConnection.disconnect();
}
protected HttpURLConnection openURL(URL url) throws Exception {
return (HttpURLConnection) url.openConnection();
}
private void testSync(int numRequests) throws Exception {
for (int i = 0; i < numRequests; ++i) {
LOG.trace("Sending request " + i);
HttpResponse response = get(resolveURI(Constants.Router.GATEWAY_DISCOVERY_NAME,
String.format("%s/%s-%d", "/v1/ping", "sync", i)));
Assert.assertEquals(HttpResponseStatus.OK.getCode(), response.getStatusLine().getStatusCode());
}
}
private void testSyncServiceUnavailable() throws Exception {
for (int i = 0; i < 25; ++i) {
LOG.trace("Sending request " + i);
HttpResponse response = get(resolveURI(DEFAULT_SERVICE, String.format("%s/%s-%d", "/v1/ping", "sync", i)));
Assert.assertEquals(HttpResponseStatus.SERVICE_UNAVAILABLE.getCode(), response.getStatusLine().getStatusCode());
}
}
private byte [] generatePostData() {
byte [] bytes = new byte [MAX_UPLOAD_BYTES];
for (int i = 0; i < MAX_UPLOAD_BYTES; ++i) {
bytes[i] = (byte) i;
}
return bytes;
}
private static class ByteEntityWriter implements Request.EntityWriter {
private final byte [] bytes;
private ByteEntityWriter(byte[] bytes) {
this.bytes = bytes;
}
@Override
public void writeEntity(OutputStream out) throws IOException {
for (int i = 0; i < MAX_UPLOAD_BYTES; i += CHUNK_SIZE) {
out.write(bytes, i, CHUNK_SIZE);
}
}
}
private HttpResponse get(String url) throws Exception {
return get(url, null);
}
private HttpResponse get(String url, Header[] headers) throws Exception {
DefaultHttpClient client = getHTTPClient();
HttpGet get = new HttpGet(url);
if (headers != null) {
get.setHeaders(headers);
}
return client.execute(get);
}
/**
* A server for the router.
*/
public abstract static class RouterService extends AbstractIdleService {
public abstract int lookupService(String serviceName);
}
/**
* A generic server for testing router.
*/
public static class ServerService extends AbstractIdleService {
private static final Logger log = LoggerFactory.getLogger(ServerService.class);
private final String hostname;
private final DiscoveryService discoveryService;
private final Supplier<String> serviceNameSupplier;
private final AtomicInteger numRequests = new AtomicInteger(0);
private final AtomicInteger numConnectionsOpened = new AtomicInteger(0);
private final AtomicInteger numConnectionsClosed = new AtomicInteger(0);
private NettyHttpService httpService;
private Cancellable cancelDiscovery;
private ServerService(String hostname, DiscoveryService discoveryService, Supplier<String> serviceNameSupplier) {
this.hostname = hostname;
this.discoveryService = discoveryService;
this.serviceNameSupplier = serviceNameSupplier;
}
@Override
protected void startUp() {
NettyHttpService.Builder builder = NettyHttpService.builder();
builder.addHttpHandlers(ImmutableSet.of(new ServerHandler()));
builder.setHost(hostname);
builder.setPort(0);
builder.modifyChannelPipeline(new Function<ChannelPipeline, ChannelPipeline>() {
@Nullable
@Override
public ChannelPipeline apply(ChannelPipeline input) {
input.addLast("connection-counter", new SimpleChannelHandler() {
@Override
public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
numConnectionsOpened.incrementAndGet();
super.channelOpen(ctx, e);
}
@Override
public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
numConnectionsClosed.incrementAndGet();
super.channelClosed(ctx, e);
}
});
return input;
}
});
httpService = builder.build();
httpService.startAndWait();
registerServer();
log.info("Started test server on {}", httpService.getBindAddress());
}
@Override
protected void shutDown() {
cancelDiscovery.cancel();
httpService.stopAndWait();
}
public int getNumRequests() {
return numRequests.get();
}
public int getNumConnectionsOpened() {
return numConnectionsOpened.get();
}
public int getNumConnectionsClosed() {
return numConnectionsClosed.get();
}
public void clearState() {
numRequests.set(0);
numConnectionsOpened.set(0);
numConnectionsClosed.set(0);
}
public void registerServer() {
// Register services of test server
log.info("Registering service {}", serviceNameSupplier.get());
cancelDiscovery = discoveryService.register(ResolvingDiscoverable.of(new Discoverable() {
@Override
public String getName() {
return serviceNameSupplier.get();
}
@Override
public InetSocketAddress getSocketAddress() {
return httpService.getBindAddress();
}
}));
}
public void cancelRegistration() {
log.info("Cancelling discovery registration of service {}", serviceNameSupplier.get());
cancelDiscovery.cancel();
}
/**
* Simple handler for server.
*/
public class ServerHandler extends AbstractHttpHandler {
private final Logger log = LoggerFactory.getLogger(ServerHandler.class);
@GET
@Path("/v1/ping/{text}")
public void ping(@SuppressWarnings("UnusedParameters") HttpRequest request, final HttpResponder responder,
@PathParam("text") String text) {
numRequests.incrementAndGet();
log.trace("Got text {}", text);
responder.sendString(HttpResponseStatus.OK, serviceNameSupplier.get());
}
@GET
@Path("/abc/v1/ping/{text}")
public void abcPing(@SuppressWarnings("UnusedParameters") HttpRequest request, final HttpResponder responder,
@PathParam("text") String text) {
numRequests.incrementAndGet();
log.trace("Got text {}", text);
responder.sendString(HttpResponseStatus.OK, serviceNameSupplier.get());
}
@GET
@Path("/def/v1/ping/{text}")
public void defPing(@SuppressWarnings("UnusedParameters") HttpRequest request, final HttpResponder responder,
@PathParam("text") String text) {
numRequests.incrementAndGet();
log.trace("Got text {}", text);
responder.sendString(HttpResponseStatus.OK, serviceNameSupplier.get());
}
@GET
@Path("/v2/ping")
public void gateway(@SuppressWarnings("UnusedParameters") HttpRequest request, final HttpResponder responder) {
numRequests.incrementAndGet();
responder.sendString(HttpResponseStatus.OK, serviceNameSupplier.get());
}
@GET
@Path("/abc/v1/status")
public void abcStatus(HttpRequest request, HttpResponder responder) {
numRequests.incrementAndGet();
responder.sendStatus(HttpResponseStatus.OK);
}
@GET
@Path("/def/v1/status")
public void defStatus(HttpRequest request, HttpResponder responder) {
numRequests.incrementAndGet();
responder.sendStatus(HttpResponseStatus.OK);
}
@GET
@Path("/v1/timeout/{timeout-millis}")
public void timeout(HttpRequest request, HttpResponder responder,
@PathParam("timeout-millis") int timeoutMillis) throws InterruptedException {
numRequests.incrementAndGet();
TimeUnit.MILLISECONDS.sleep(timeoutMillis);
responder.sendStatus(HttpResponseStatus.OK);
}
@POST
@Path("/v1/upload")
public void upload(HttpRequest request, HttpResponder responder) throws IOException {
ChannelBuffer content = request.getContent();
int readableBytes;
ChunkResponder chunkResponder = responder.sendChunkStart(HttpResponseStatus.OK,
ImmutableMultimap.<String, String>of());
while ((readableBytes = content.readableBytes()) > 0) {
int read = Math.min(readableBytes, CHUNK_SIZE);
chunkResponder.sendChunk(content.readSlice(read));
//TimeUnit.MILLISECONDS.sleep(RANDOM.nextInt(1));
}
chunkResponder.close();
}
}
}
}